/*-
 * Copyright (c) 2010 mod_cfp team <mod-cfp@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: mod_cfp.c 173 2010-07-13 19:05:02Z mod.cfp $
 */

#include <sys/param.h>
# include <sys/socket.h>
# include <sys/wait.h>
# include <sys/mman.h>
# include <netinet/in.h>
# include <arpa/inet.h>
#ifdef HAVE_SYS_FILIO_H
# include <sys/filio.h>
#endif

#include <assert.h>
#include <ctype.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <fdevent.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

#include "server.h"
#include "stat_cache.h"
#include "keyvalue.h"
#include "log.h"
#include "connections.h"
#include "joblist.h"
#include "http_chunk.h"
#include "plugin.h"
#include "version.h"
#include "mod_cfp.h"

struct cfp_priv {
	server			*srv;
	connection		*con;
	buffer			*response;
	int			fde_ndx;
	int			fd;		/* DELETEME for debugging */
};

struct cfp_rfc1867 {
	struct cfp_sess		*sp;
	chunk			*c;
	const char		*ptr;
	size_t			len;
	off_t			offset;
	const char		*boundary;
	size_t			boundary_len;
};

typedef struct {
	buffer			*module;
	struct cfp_module	*mod;
	void			*dlh;
	struct cfp_conf		cfp_conf;
} plugin_config;

typedef struct {
	PLUGIN_DATA;
	plugin_config		**config_storage;
	plugin_config		conf;
} plugin_data;

int	mod_cfp_plugin_init(plugin *p);

/* ------------------------------------------------------------------------- */

/* XXX move where? */
void
cfp_log(struct cfp_module *cm, const char *fmt, ...)
{
	server *srv = (server *)cm->srv_arg;
	va_list ap;
	char buf[8192];

	assert(fmt != NULL);
	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf), fmt, ap);
	log_error_write(srv, __FILE__, __LINE__, "s", buf);
	va_end(ap);
}

int
cfp_fprintf(struct cfp_sess *sp, const char *fmt, ...)
{
	struct cfp_priv *pp = (struct cfp_priv *)sp->srv_arg;
	buffer *msg = pp->response;
	va_list ap;
	int len;
	char buf[BUFSIZ];

	/* XXX to minimize the response time for the first byte. */
	assert(fmt != NULL);
	va_start(ap, fmt);
	len = vsnprintf(buf, BUFSIZ, fmt, ap);
	if (len >= BUFSIZ) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}
	buffer_prepare_append(msg, len + 1);
	memcpy(msg->ptr + msg->used, buf, len);
	msg->used += len;
	va_end(ap);

	return (len);
}

#define	CFP_GET_VALUE(name, value)					\
int									\
cfp_get_##name(struct cfp_sess *sp, struct cfp_txt *txt)		\
{									\
	struct cfp_priv *pp = (struct cfp_priv *)sp->srv_arg;		\
	connection *con = pp->con;					\
									\
	if (txt == NULL) {						\
		sp->errnum = CFP_EINVAL;				\
		return (-1);						\
	}								\
	if (buffer_is_empty(value)) {					\
		txt->b = txt->e;					\
		return (0);						\
	}								\
	txt->b = (value)->ptr;						\
	txt->e = (value)->ptr + (value)->used;				\
	return (0);							\
}

CFP_GET_VALUE(request_origuri, con->request.orig_uri)
CFP_GET_VALUE(requrl, con->uri.path)
CFP_GET_VALUE(querystring, con->uri.query)

int
cfp_get_request_method(struct cfp_sess *sp, struct cfp_txt *txt)
{
	struct cfp_priv *pp = (struct cfp_priv *)sp->srv_arg;
	connection *con = pp->con;
	const char *s;

	if (txt == NULL) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}
	s = get_http_method_name(con->request.http_method);
	assert(s != NULL);
	txt->b = s;
	txt->e = s + strlen(s);
	return (0);
}

int
cfp_get_query(struct cfp_sess *sp, const char *key, struct cfp_txt *txt)
{
	unsigned i, keylen, len;

	if (key == NULL || txt == NULL) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}

	keylen = strlen(key);
	for (i = 0; i < sp->nquery; i++) {
		len = MAX(keylen, CFP_TXT_LEN(&(sp->query[i].key)));
		if (!strncasecmp(key, sp->query[i].key.b, len)) {
			txt->b = sp->query[i].value.b;
			txt->e = sp->query[i].value.e;
			return (0);
		}
	}
	sp->errnum = CFP_ENOENT;
	return (-1);
}

int
cfp_get_postquery(struct cfp_sess *sp, const char *key, struct cfp_txt *txt)
{
	struct cfp_priv *pp = (struct cfp_priv *)sp->srv_arg;
	connection *con = pp->con;
	unsigned i, keylen, len;

	if (key == NULL || txt == NULL) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}

	if (con->request.http_method != HTTP_METHOD_POST) {
		sp->errnum = CFP_EPERM;
		return (-1);
	}

	keylen = strlen(key);
	for (i = 0; i < sp->npost_query; i++) {
		len = MAX(keylen, CFP_TXT_LEN(&(sp->post_query[i].key)));
		if (!strncasecmp(key, sp->post_query[i].key.b, len)) {
			txt->b = sp->post_query[i].value.b;
			txt->e = sp->post_query[i].value.e;
			return (0);
		}
	}
	sp->errnum = CFP_ENOENT;
	return (-1);
}

int
cfp_getenv(struct cfp_sess *sp, const char *key, struct cfp_txt *txt)
{
	struct cfp_priv *pp = (struct cfp_priv *)sp->srv_arg;
	connection *con = pp->con;
	data_string *ds;
	unsigned i, keylen, len;

	if (key == NULL || txt == NULL) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}

	keylen = strlen(key);
	for (i = 0; i < con->environment->used; i++) {
		ds = (data_string *)con->environment->data[i];
		if (ds->value->used && ds->key->used) {
			len = MAX(keylen, ds->key->used);
			/* environement variables look case-sensitive */
			if (!strncmp(key, ds->key->ptr, len)) {
				txt->b = ds->value->ptr;
				txt->b = ds->value->ptr + ds->value->used;
				return (0);
			}
		}
	}
	sp->errnum = CFP_ENOENT;
	return (-1);
}

int
cfp_get_header(struct cfp_sess *sp, const char *key, struct cfp_txt *txt)
{
	struct cfp_priv *pp = (struct cfp_priv *)sp->srv_arg;
	connection *con = pp->con;
	data_string *ds;
	unsigned i, keylen, len;

	if (key == NULL || txt == NULL) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}

	keylen = strlen(key);
	for (i = 0; i < con->request.headers->used; i++) {
		ds = (data_string *)con->request.headers->data[i];
		if (ds->value->used && ds->key->used) {
			len = MAX(keylen, ds->key->used);
			if (!strncasecmp(key, ds->key->ptr, len)) {
				txt->b = ds->value->ptr;
				txt->b = ds->value->ptr + ds->value->used;
				return (0);
			}
		}
	}
	sp->errnum = CFP_ENOENT;
	return (-1);
}

int
cfp_set_status(struct cfp_sess *sp, int status)
{
	struct cfp_priv *pp = (struct cfp_priv *)sp->srv_arg;
	connection *con = pp->con;

	if (status < 100 && status >= 1000) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}

	con->parsed_response |= HTTP_STATUS;
	con->http_status = status;
	return (0);
}

int
cfp_set_header(struct cfp_sess *sp, const char *key, const char *value)
{
	struct cfp_priv *pp = (struct cfp_priv *)sp->srv_arg;
	connection *con = pp->con;
	data_string *ds;
	size_t key_len;
	char *end;

	if (sp == NULL || key == NULL || value == NULL) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}
	key_len = strlen(key);
	ds = (data_string *)array_get_unused_element(con->response.headers,
	    TYPE_STRING);
	if (ds == NULL)
		ds = data_response_init();
	buffer_copy_string_len(ds->key, key, key_len);
	buffer_copy_string(ds->value, value);
	array_insert_unique(con->response.headers, (data_unset *)ds);
	switch (key_len) {
	case 4:
		if (strncasecmp(key, "Date", key_len) == 0)
			con->parsed_response |= HTTP_DATE;
		break;
	case 6:
		if (strncasecmp(key, "Status", key_len) == 0) {
			errno = 0;
			con->http_status = strtol(value, &end, 10);
			if (errno == ERANGE || value == end) {
				sp->errnum = CFP_EINVAL;
				return (-1);
			}
			con->parsed_response |= HTTP_STATUS;
		}
		break;
	case 8:
		if (strncasecmp(key, "Location", key_len) == 0)
			con->parsed_response |= HTTP_LOCATION;
		break;
	case 10:
		if (strncasecmp(key, "Connection", key_len) == 0) {
			con->response.keep_alive =
			    (strcasecmp(value, "Keep-Alive") == 0) ? 1 : 0;
			con->parsed_response |= HTTP_CONNECTION;
		}
		break;
	case 14:
		if (strncasecmp(key, "Content-Length", key_len) == 0) {
			errno = 0;
			con->response.content_length = strtol(value, &end, 10);
			if (errno == ERANGE || value == end) {
				sp->errnum = CFP_EINVAL;
				return (-1);
			}
			con->parsed_response |= HTTP_CONTENT_LENGTH;
		}
		break;
	default:
		break;
	}
	return (0);
}

/* ------------------------------------------------------------------------- */

INIT_FUNC(mod_cfp_init) {
	plugin_data *p;

	p = calloc(1, sizeof(*p));
	assert(p != NULL);
	return (p);
}

FREE_FUNC(mod_cfp_cleanup) {
	plugin_config *s;
	plugin_data *p = p_d;
	size_t i;

	if (p->config_storage != NULL) {
		for (i = 0; i < srv->config_context->used; i++) {
			s = p->config_storage[i];
			if (s != NULL) {
				if (!buffer_is_empty(s->module)) {
					if (s->cfp_conf.detach != NULL)
						s->cfp_conf.detach(s->mod);
					dlclose(s->dlh);
				}
				buffer_free(s->module);
				free(s);
			}
		}
		free(p->config_storage);
	}

	free(p);

	return (HANDLER_GO_ON);
}

SETDEFAULTS_FUNC(mod_cfp_set_defaults) {
	struct cfp_conf const *cnf;
	struct cfp_module *cm;
	config_values_t cv[] = {
		{ "cfp.module",	NULL, T_CONFIG_STRING,	T_CONFIG_SCOPE_SERVER },
		{ NULL,		NULL, T_CONFIG_UNSET,	T_CONFIG_SCOPE_UNSET }
	};
	plugin_config *s;
	plugin_data *p = p_d;
	size_t i;
	int ret;

	if (p == NULL)
		return (HANDLER_ERROR);

	p->config_storage = calloc(srv->config_context->used,
	    sizeof(specific_config *));
	assert(p->config_storage != NULL);

	for (i = 0; i < srv->config_context->used; i++) {
		s = calloc(1, sizeof(plugin_config));
		assert(s != NULL);
		s->module = buffer_init();

		cv[0].destination = s->module;
		p->config_storage[i] = s;

		ret = config_insert_values_global(srv,
		    ((data_config *)srv->config_context->data[i])->value, cv);
		if (ret != 0)
			return (HANDLER_ERROR);

		if (!buffer_is_empty(s->module)) {
			s->dlh = dlopen(s->module->ptr, RTLD_NOW | RTLD_LOCAL);
			if (s->dlh == NULL) {
				log_error_write(srv, __FILE__, __LINE__, "ss",
				    "dlopen(3) error:", dlerror());
				return (HANDLER_ERROR);
			}
			cnf = dlsym(s->dlh, "conf");
			if (cnf == NULL) {
				log_error_write(srv, __FILE__, __LINE__, "ss",
				    "dlsym(3) error:", dlerror());
				return (HANDLER_ERROR);
			}
			assert(cnf->magic == CFP_CONF_MAGIC);

			memcpy(&s->cfp_conf, cnf, sizeof(*cnf));

			cm = calloc(1, sizeof(*cm));
			assert(cm != NULL);
			cm->magic = CFP_MODULE_MAGIC;
			cm->srv_arg = srv;
			s->mod = cm;

			if (s->cfp_conf.attach != NULL)
				s->cfp_conf.attach(cm);
		}
	}
	return (HANDLER_GO_ON);
}

static int
mod_cfp_patch_connection(server *srv, connection *con, plugin_data *p)
{
#define PATCH(x)	(p->conf.x = s->x)
	data_config *dc;
	data_unset *du;
	plugin_config *s = p->config_storage[0];
	size_t i, j;

	PATCH(module);
	PATCH(mod);
	PATCH(dlh);
	/* XXX large copy?? */
	PATCH(cfp_conf);

	for (i = 1; i < srv->config_context->used; i++) {
		dc = (data_config *)srv->config_context->data[i];
		s = p->config_storage[i];

		if (!config_check_cond(srv, con, dc))
			continue;
		for (j = 0; j < dc->value->used; j++) {
			du = dc->value->data[j];

			if (buffer_is_equal_string(du->key,
			    CONST_STR_LEN("c.module"))) {
				PATCH(module);
				PATCH(mod);
				PATCH(dlh);
				/* XXX large copy?? */
				PATCH(cfp_conf);
			}
		}
	}

	return (0);
#undef PATCH
}

static int
mod_cfp_parsequery(server *srv, const char *buf, size_t buflen,
    struct cfp_pair **pair0, unsigned *npair0)
{
#define	CFP_MAX_QUERIES		1024
	struct cfp_pair *pair;
	struct cfp_txt key[CFP_MAX_QUERIES];
	struct cfp_txt value[CFP_MAX_QUERIES];
	int equal, i, idx, nq;
	const char *b, *e, *p;
	const char *sep[CFP_MAX_QUERIES];

	(void)srv;

	if (buflen == 0) {
		*npair0 = 0;
		return (0);
	}

	b = buf;
	e = buf + buflen;
	for (p = b, idx = 0; p < e; p++) {
		if (idx >= CFP_MAX_QUERIES)
			break;
		if (!(*p == '&' || (*p == '?' && p == b)))
			continue;
		sep[idx++] = p;
	}
	if (idx < CFP_MAX_QUERIES)
		sep[idx++] = e;

	for (i = 0, nq = 0; i < idx; i++) {
		if ((sep[i] - sep[i - 1]) <= 1)
			continue;
		equal = 0;
		if (i == 0) {
			b = buf;
			e = sep[i];
		} else {
			b = sep[i - 1] + 1;
			e = sep[i];
		}
		for (p = b; p < e; p++) {
			if (*p == '=') {
				equal = 1;
				key[nq].b = b;
				key[nq].e = p;
				value[nq].b = p + 1;
				value[nq].e = e;
				break;
			}
		}
		if (equal == 0) {
			key[nq].b = b;
			key[nq].e = e;
			value[nq].b = NULL;
			value[nq].e = NULL;
		}
		nq++;
	}

	if (nq == 0) {
		*npair0 = 0;
		return (0);
	}

	/* XXX why we give a change for the program to call brk(2).  */
	*pair0 = pair = malloc(sizeof(struct cfp_pair) * nq);
	assert(pair != NULL);

	for (i = 0; i < nq; i++) {
		pair[i].key.b = key[i].b;
		pair[i].key.e = key[i].e;
		pair[i].value.b = value[i].b;
		pair[i].value.e = value[i].e;
	}
	*npair0 = nq;
	return (0);
#undef CFP_MAX_QUERIES
}

static int
mod_cfp_contentqueue_init(server *srv, connection *con, struct cfp_sess *sp,
    struct cfp_rfc1867 *rfc1867, const char *boundary, size_t boundary_len)
{
	chunkqueue *cq;
	chunk *c;

	cq = con->request_content_queue;
	assert(chunkqueue_length(cq) == (off_t)con->request.content_length);
	rfc1867->sp = sp;
	rfc1867->c = cq->first;
	rfc1867->ptr = NULL;
	rfc1867->len = 0;
	rfc1867->offset = -1;
	rfc1867->boundary = boundary;
	rfc1867->boundary_len = boundary_len;

	for (c = cq->first; c; c = c->next) {
		switch (c->type) {
		case FILE_CHUNK:
			if (c->file.mmap.start == MAP_FAILED) {
				if (c->file.fd == -1 &&
				    (c->file.fd = open(c->file.name->ptr,
					O_RDONLY)) == -1) {
					log_error_write(srv,
					    __FILE__, __LINE__, "ss",
					    "open failed: ", strerror(errno));
					return (-1);
				}
				c->file.mmap.length = c->file.length;
				c->file.mmap.start = mmap(0,
				    c->file.mmap.length, PROT_READ, MAP_SHARED,
				    c->file.fd, 0);
				if (c->file.mmap.start == MAP_FAILED) {
					log_error_write(srv,
					    __FILE__, __LINE__, "ssbd",
					    "mmap failed: ", strerror(errno),
					    c->file.name,  c->file.fd);
					return (-1);
				}
				close(c->file.fd);
				c->file.fd = -1;
				/*
				 * chunk_reset() or chunk_free() will
				 * cleanup for us
				 */
			}
			break;
		default:
			break;
		}
	}
	return (0);
}


static int
mod_cfp_contentqueue_eof(struct cfp_rfc1867 *rfc1867)
{

	assert(rfc1867->offset < (off_t)rfc1867->len);
	assert(rfc1867->len > 0);
	if (rfc1867->c == NULL && rfc1867->offset == (off_t)(rfc1867->len - 1))
		return (1);
	return (0);
}

static int
mod_cfp_contentqueue_fill(struct cfp_rfc1867 *rfc1867)
{
	chunk *c = rfc1867->c;

	if (c == NULL)
		return (-1);

	switch (c->type) {
	case FILE_CHUNK:
		rfc1867->ptr = c->file.mmap.start;
		rfc1867->len = c->file.length;
		break;
	case MEM_CHUNK:
		rfc1867->ptr = c->mem->ptr;
		rfc1867->len = c->mem->used;
		break;
	default:
		/* Only handles FILE_CHUNK and MEM_CHUNK */
		assert(0 == 1);
	}
	rfc1867->offset = 0;
	rfc1867->c = c->next;
	assert(rfc1867->c == NULL);
	return (0);
}

static int
mod_cfp_contentqueue_skipboundary(struct cfp_rfc1867 *rfc1867)
{
	const char *p;
	int ret;

	assert(rfc1867->offset + 2 + rfc1867->boundary_len +
	    2 <= rfc1867->len - 1);
	p = rfc1867->ptr;

	if (p[rfc1867->offset] == '-' && p[rfc1867->offset + 1] == '-') {
		ret = memcmp(&p[rfc1867->offset + 2], rfc1867->boundary,
		    rfc1867->boundary_len);
		if (ret == 0) {
			rfc1867->offset += 2 + rfc1867->boundary_len;
			if (p[rfc1867->offset] == '\r' &&
			    p[rfc1867->offset + 1] == '\n') {
				rfc1867->offset += 2;
				return (0);
			}
			if (p[rfc1867->offset] == '-' &&
			    p[rfc1867->offset + 1] == '-') {
				rfc1867->offset += 2;
				return (1);
			}
			assert(0 == 1);
			return (-1);
		}
	}
	assert(0 == 1);
	return (-1);
}

static int
mod_cfp_contentqueue_getline(struct cfp_rfc1867 *rfc1867, const char **b,
    const char **e)
{
	const char *p;
	size_t i;

	p = rfc1867->ptr;
	*b = p + rfc1867->offset;

	for (i = rfc1867->offset; i < rfc1867->len; i++) {
		if (p[i] == '\r' && p[i + 1] == '\n') {
			*e = &p[i];
			rfc1867->offset = i + 2;
			return (0);
		}
	}
	assert(0 == 1);
	return (-1);
}

static int
mod_cfp_parsepost_rfc1867(server *srv, connection *con, struct cfp_sess *sp)
{
#define	CFP_MAX_QUERIES		1024
	struct cfp_pair *pair;
	struct cfp_rfc1867 rfc1867;
	struct cfp_txt key[CFP_MAX_QUERIES];
	struct cfp_txt value[CFP_MAX_QUERIES];
	const char *b, *e, *boundary, *p, *r, *content_type;
	size_t boundary_len, i, nq;
	int ret;

	(void)sp;

	content_type = con->request.http_content_type;
	boundary = strcasestr(content_type, "boundary");
	if (boundary == NULL) {
		log_error_write(srv, __FILE__, __LINE__, "s",
		    "Missing boundary in multipart/form-data POST data");
		return (-1);
	}
	boundary = strchr(boundary, '=');
	if (boundary == NULL) {
		log_error_write(srv, __FILE__, __LINE__, "s",
		    "Missing boundary seperator in multipart/form-data"
		    " POST data");
		return (-1);
	}
	boundary++;
	p = NULL;
	if (boundary[0] == '"') {
		boundary++;
		p = strchr(boundary, '"');
		if (p == NULL) {
			log_error_write(srv, __FILE__, __LINE__, "s",
			    "Invalid boundary in multipart/form-data "
			    " POST data");
			return (-1);
		}
	} else
		p = strchr(boundary, ',');
	if (p != NULL)
		boundary_len = p - boundary;
	else
		boundary_len = strlen(boundary);

	nq = 0;
	mod_cfp_contentqueue_init(srv, con, sp, &rfc1867, boundary,
	    boundary_len);
	mod_cfp_contentqueue_fill(&rfc1867);
	while (!mod_cfp_contentqueue_eof(&rfc1867)) {
		ret = mod_cfp_contentqueue_skipboundary(&rfc1867);
		assert(ret == 0 || ret == 1);
		if (ret == 1)
			break;
		do {
			b = e = NULL;
			ret = mod_cfp_contentqueue_getline(&rfc1867, &b, &e);
			assert(ret == 0);
			ret = strncasecmp(b, "Content-Disposition:",
			    sizeof("Content-Disposition:") - 1);
			if (ret == 0) {
				p = strstr(b +
				    sizeof("Content-Disposition:") - 1,
				    "name");
				assert(p != NULL);
				p = strchr(p, '=');
				assert(p != NULL);
				p++;
				assert(*p == '"');
				p++;
				r = strchr(p, '"');
				assert(r != NULL);
				key[nq].b = p;
				key[nq].e = r;
			}
		} while ((e - b) != 0);

		/* reads form-data */
		p = rfc1867.ptr;
		b = rfc1867.ptr + rfc1867.offset;
		e = NULL;

		/*
		 * XXX
		 * During traversing the the following chunk and if the chunk
		 * was a file chunk whose size was over 4 GB, it means all
		 * content body was tried to be on the memory by page faults
		 * because we're using mmap(2).
		 *
		 * We need to find a efficient way to find the end of form-data
		 * without holding large residental memory.
		 */
		for (i = rfc1867.offset; i < rfc1867.len; i++) {
			if (p[i] == '\r' && p[i + 1] == '\n' &&
			    p[i + 2] == '-' && p[i + 3] == '-') {
				/*
				 * XXX after this memcmp(3) it'll retry to
				 * compare buffer at
				 *     mod_cfp_contentqueue_skipboundary()
				 * we need to avoid this ops.
				 */
				ret = memcmp(&p[i + 4], rfc1867.boundary,
				    rfc1867.boundary_len);
				if (ret == 0) {
					e = &p[i];
					rfc1867.offset = i + 2;
					break;
				}
			}
		}
		if (e != NULL) {
			value[nq].b = b;
			value[nq].e = e;
			nq++;
		}
	}

	/* XXX why we give a change for the program to call brk(2).  */
	sp->post_query = pair = malloc(sizeof(struct cfp_pair) * nq);
	assert(pair != NULL);
	for (i = 0; i < nq; i++) {
		pair[i].key.b = key[i].b;
		pair[i].key.e = key[i].e;
		pair[i].value.b = value[i].b;
		pair[i].value.e = value[i].e;
	}
	sp->npost_query = nq;
	return (0);
#undef CFP_MAX_QUERIES
}

static int
mod_cfp_parsepost_std(server *srv, connection *con, struct cfp_sess *sp)
{
	chunk *c;
	chunkqueue *cq;
	size_t length;
	int ret;
	char *p;

	(void)sp;

	cq = con->request_content_queue;
	assert(chunkqueue_length(cq) == (off_t)con->request.content_length);

	c = cq->first;
	assert(c->next == NULL);
	switch (c->type) {
	case FILE_CHUNK:
		if (c->file.mmap.start == MAP_FAILED) {
			if (c->file.fd == -1 &&
			    (c->file.fd = open(c->file.name->ptr,
				O_RDONLY)) == -1) {
				log_error_write(srv,
				    __FILE__, __LINE__, "ss",
				    "open failed: ", strerror(errno));
				return (-1);
			}
			c->file.mmap.length = c->file.length;
			c->file.mmap.start = mmap(0,
			    c->file.mmap.length, PROT_READ, MAP_SHARED,
			    c->file.fd, 0);
			if (c->file.mmap.start == MAP_FAILED) {
				log_error_write(srv,
				    __FILE__, __LINE__, "ssbd",
				    "mmap failed: ", strerror(errno),
				    c->file.name,  c->file.fd);
				return (-1);
			}
			close(c->file.fd);
			c->file.fd = -1;
			/*
			 * chunk_reset() or chunk_free() will
			 * cleanup for us
			 */
		}
		p = c->file.mmap.start;
		length = c->file.length;
		break;
	case MEM_CHUNK:
		p = c->mem->ptr;
		length = c->mem->used;
		break;
	default:
		p = NULL;
		length = 0;
		/* Only handles FILE_CHUNK and MEM_CHUNK */
		assert(0 == 1);
	}

	/* NB: in this process there are no decoding queries.  */
	ret = mod_cfp_parsequery(srv, p, length, &sp->post_query,
	    &sp->npost_query);
	assert(ret != -1);
	return (0);
}

static int
mod_cfp_parsepost(server *srv, connection *con, struct cfp_sess *sp)
{
	data_string *ds;
	unsigned len;

	(void)sp;

	if (con->request.http_method != HTTP_METHOD_POST)
		return (0);
	if (con->request.content_length == 0)
		return (0);

	ds = (data_string *)array_get_element(con->request.headers,
	    "Content-Type");
	if (ds != NULL) {
		len = MAX(ds->value->used, sizeof("multipart/form-data") -1 );
		if (strcasestr(ds->value->ptr, "multipart/form-data") != NULL)
			return (mod_cfp_parsepost_rfc1867(srv, con, sp));
	}
	return (mod_cfp_parsepost_std(srv, con, sp));
}

static struct cfp_sess *
mod_cfp_sessnew(server *srv, connection *con, plugin_data *p,
    struct cfp_conf *conf)
{
	struct cfp_sess *sp;
	struct cfp_priv *pp;
	int ret;

	(void)srv;

	assert(!buffer_is_empty(con->uri.path));

	sp = calloc(1, sizeof(*sp) + sizeof(*pp));
	assert(sp != NULL);
	pp = (struct cfp_priv *)(sp + 1);
	pp->srv = srv;
	pp->con = con;
	pp->response = buffer_init();

	sp->module = p->conf.mod;
	sp->conf = conf;
	sp->srv_arg = pp;
	ret = mod_cfp_parsequery(srv, con->uri.query->ptr,
	    con->uri.query->used, &sp->query, &sp->nquery);
	assert(ret != -1);
	ret = mod_cfp_parsepost(srv, con, sp);
	assert(ret == 0);
	return (sp);
}

static void
mod_cfp_sessfree(struct cfp_sess *sp)
{
	struct cfp_priv *pp;

	assert(sp != NULL);
	pp = sp->srv_arg;
	buffer_free(pp->response);
	free(sp->post_query);
	free(sp->query);
	free(sp);
}

static handler_t
mod_cfp_handle_uri_clean(server *srv, connection *con, void *p_d)
{
	struct cfp_conf *cnf;
	struct cfp_sess *sp;
	plugin_data *p = p_d;
	int ret;

	if (con->mode != DIRECT)
		return (HANDLER_GO_ON);
	/* XXX checked? */
	if (con->file_started == 1)
		return (HANDLER_GO_ON);

	mod_cfp_patch_connection(srv, con, p);

	cnf = &p->conf.cfp_conf;
	assert(cnf != NULL);
	/*
	 * XXX I think we need to seperate mod_cfp_sessnew() into two parts
	 * for efficiency that some variables could have reasons when
	 * lookup was successful.
	 */
	sp = mod_cfp_sessnew(srv, con, p, cnf);
	assert(sp != NULL);
	assert(con->plugin_ctx[p->id] == NULL);
	con->plugin_ctx[p->id] = sp;

	if (cnf->lookup != NULL) {
		ret = cnf->lookup(sp);
		if (ret == CFP_MATCH_OK) {
			sp->flags |= CFP_SESS_FLAGS_MATCHED;
			con->mode = p->id;
			if (cnf->init != NULL) {
				/* XXX currently no checking the return value */
				(void)cnf->init(sp);
			}
		} else {
			mod_cfp_sessfree(sp);
			con->plugin_ctx[p->id] = NULL;
		}
	}

	return (HANDLER_GO_ON);
}

static handler_t
mod_cfp_fdevent_handler(void *s, void *ctx, int revents) {
	struct cfp_sess *sp = (struct cfp_sess *)ctx;
	struct cfp_priv *pp = (struct cfp_priv *)sp->srv_arg;
	connection *con = pp->con;
	server *srv = (server *)s;

	(void)revents;

	assert(sp->fd == pp->fd);
	assert(pp->fde_ndx != -1);

	joblist_append(srv, con);
	fdevent_event_del(srv->ev, &(pp->fde_ndx), sp->fd);
	fdevent_unregister(srv->ev, sp->fd);
	sp->fd = -1;
	return (HANDLER_GO_ON);
}

static handler_t
mod_cfp_handle_subrequest(server *srv, connection *con, void *p_d)
{
	struct cfp_conf *cnf;
	struct cfp_sess *sp;
	struct cfp_priv *pp;
	plugin_data *p = p_d;
	int ret;

	(void)srv;
	if (con->mode != p->id)
		return (HANDLER_GO_ON);

	sp = con->plugin_ctx[p->id];
	if (sp == NULL)
		return (HANDLER_GO_ON);
	pp = (struct cfp_priv *)sp->srv_arg;

	cnf = &p->conf.cfp_conf;
	assert(cnf != NULL);
	if (cnf->start != NULL && (sp->flags & CFP_SESS_FLAGS_MATCHED)) {
		if (con->file_started == 0)
			con->file_started = 1;

		ret = cnf->start(sp);
		switch (ret) {
		case CFP_RET_SOCKET_READABLE:
		case CFP_RET_SOCKET_WRITABLE:
			pp->fde_ndx = -1;
			pp->fd = sp->fd;
			/* XXX it calls calloc(3) */
			fdevent_register(srv->ev, sp->fd,
			    mod_cfp_fdevent_handler, sp);
			fdevent_event_add(srv->ev, &(pp->fde_ndx), sp->fd,
			    (ret == CFP_RET_SOCKET_READABLE) ? FDEVENT_IN :
			    FDEVENT_OUT);
			if (fdevent_fcntl_set(srv->ev, sp->fd) == -1) {
				/* XXX todo */
				assert(0 == 1);
			}
			return (HANDLER_WAIT_FOR_EVENT);
		case CFP_RET_DISK_READ:
		case CFP_RET_DISK_WRITE:
			/* XXX not supporting asynchronous disk I/Os */
			assert(0 == 1);
			break;
		case CFP_RET_DONE:
			con->file_finished = 1;
			/*
			 * XXX why pp->response->used + 1? but it's working
			 * expected.
			 */
			http_chunk_append_mem(srv, con, pp->response->ptr,
			    pp->response->used + 1);
			joblist_append(srv, con);
			return (HANDLER_FINISHED);
			/* NOTREACHED */
		case CFP_RET_ERROR:
			con->http_status = 500;
			con->mode = DIRECT;
			break;
		}
	}

	return (HANDLER_GO_ON);
}

static handler_t
mod_cfp_handle_request_done(server *srv, connection *con, void *p_d)
{
	struct cfp_conf *cnf;
	struct cfp_sess *sp;
	plugin_data *p = p_d;

	(void)srv;

	if (con->mode != p->id)
		return (HANDLER_GO_ON);

	sp = con->plugin_ctx[p->id];
	if (sp != NULL) {
		cnf = &p->conf.cfp_conf;
		if (cnf->fini != NULL)
			cnf->fini(sp);

		mod_cfp_sessfree(sp);
		con->plugin_ctx[p->id] = NULL;
	}
	return (HANDLER_GO_ON);
}

static handler_t
mod_cfp_connection_reset(server *srv, connection *con, void *p_d)
{
	struct cfp_conf *cnf;
	struct cfp_sess *sp;
	plugin_data *p = p_d;

	(void)srv;

	if (con->mode != p->id)
		return (HANDLER_GO_ON);

	sp = con->plugin_ctx[p->id];
	if (sp != NULL) {
		cnf = &p->conf.cfp_conf;
		if (cnf->fini != NULL)
			cnf->fini(sp);

		mod_cfp_sessfree(sp);
		con->plugin_ctx[p->id] = NULL;
	}

	return (HANDLER_GO_ON);
}

int mod_cfp_plugin_init(plugin *p) {
	p->version = LIGHTTPD_VERSION_ID;
	p->name = buffer_init_string("cfp");

	p->init = mod_cfp_init;
	p->cleanup = mod_cfp_cleanup;
	p->set_defaults = mod_cfp_set_defaults;
	p->handle_uri_clean = mod_cfp_handle_uri_clean;
	p->handle_subrequest = mod_cfp_handle_subrequest;
	p->handle_request_done = mod_cfp_handle_request_done;
	p->connection_reset = mod_cfp_connection_reset;
	p->data = NULL;

	return (0);
}
